package com.gjy.encoder.security;

import com.sun.org.slf4j.internal.Logger;
import com.sun.org.slf4j.internal.LoggerFactory;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.interfaces.ECPrivateKey;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;
import java.util.UUID;

public class GMCertificate {
    private static final Logger logger = LoggerFactory.getLogger(GMCertificate.class);

    public static final byte[] DEFAULT_SIGN_USERID;
    public static final byte[] DEFAULT_VERIFY_USERID;
    private byte[] verifyKey = null;
    private byte[] signKey = null;
    private byte[] signUserID = null;
    private byte[] verifyUserID = null;
    private byte[] encryptKey = null;
    private String encryptMode = null;
    private byte[] decryptKey = null;
    private String decryptMode = null;

    private static X9ECParameters GM_NAME_CURVES;
    private static KeyFactory FACTORY;
    private static ECParameterSpec EC_SPEC;
    private static ECDomainParameters DOMAIN_PARAMS;

    static {
        synchronized (Security.class) {
            if (logger.isDebugEnabled()) {
                logger.debug("参数初始化...{}", GMCertificate.class.getClassLoader());
            }

            DEFAULT_SIGN_USERID = "DRs8izetSbKxZQSgVbmv6Q==".getBytes(StandardCharsets.US_ASCII);
            DEFAULT_VERIFY_USERID = DEFAULT_SIGN_USERID;
            if (Security.getProvider("BC") == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("加参初始化...");
                }
                Security.addProvider(new BouncyCastleProvider());
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("加参已经初始化...");
                }
            }


            try {
                if (logger.isDebugEnabled()) {
                    logger.debug("参数初始化...{},{},{},{}", GM_NAME_CURVES, FACTORY, EC_SPEC, DOMAIN_PARAMS);
                }
                if (null == GM_NAME_CURVES) {
                    GM_NAME_CURVES = GMNamedCurves.getByName("sm2p256v1");
                    FACTORY = KeyFactory.getInstance("EC", Security.getProvider("BC"));
                    EC_SPEC = new ECParameterSpec(GM_NAME_CURVES.getCurve(), GM_NAME_CURVES.getG(), GM_NAME_CURVES.getN(), GM_NAME_CURVES.getH());
                    DOMAIN_PARAMS = new ECDomainParameters(GM_NAME_CURVES.getCurve(), GM_NAME_CURVES.getG(), GM_NAME_CURVES.getN());
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("参数已经初始化...{},{},{},{}", GM_NAME_CURVES, FACTORY, EC_SPEC, DOMAIN_PARAMS);
                    }
                }
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public GMCertificate(byte[] signKey, byte[] signUserID, byte[] verifyKey, byte[] verifyUserID) {
        this.signKey = signKey;
        this.signUserID = signUserID;
        this.verifyKey = verifyKey;
        this.verifyUserID = verifyUserID;
    }

    public GMCertificate(byte[] encryptKey, byte[] decryptKey) {
        this.encryptKey = encryptKey;
        this.decryptKey = decryptKey;
    }

    public byte[] encrypt(byte[] msg) throws SecurityException {
        if (this.encryptKey == null) {
            throw new SecurityException("未初始化加密密钥");
        } else {
            SM2Engine.Mode mode;
            switch (this.encryptMode == null ? "C1C3C2" : this.encryptMode) {
                case "C1C2C3":
                    mode = SM2Engine.Mode.C1C2C3;
                    break;
                case "C1C3C2":
                    mode = SM2Engine.Mode.C1C3C2;
                    break;
                default:
                    throw new SecurityException("暂不支持的加密模式");
            }

            SM2Engine sm2Engine;
            Exception e;
            try {
                sm2Engine = new SM2Engine(mode);
                if (logger.isDebugEnabled()) {
                    logger.debug("encrypt mode: {}", mode);
                }

                ECCurve ecCurve = GM_NAME_CURVES.getCurve();
                if (logger.isDebugEnabled()) {
                    logger.debug("encrypt ecCurve: {}", ecCurve);
                }

                ECPoint ecPoint = ecCurve.decodePoint(this.encryptKey);
                if (logger.isDebugEnabled()) {
                    logger.debug("encrypt ecPoint: {},{}", ecPoint, this.encryptKey);
                }

                ECPublicKey keyObj = (ECPublicKey) FACTORY.generatePublic(new ECPublicKeySpec(ecPoint, EC_SPEC));
                if (logger.isDebugEnabled()) {
                    logger.debug("encrypt keyObj: {}", keyObj);
                }

                ECKeyParameters keyParam = new ECPublicKeyParameters(keyObj.getQ(), DOMAIN_PARAMS);
                if (logger.isDebugEnabled()) {
                    logger.debug("encrypt keyParam: {}", keyParam);
                }

                byte[] initKey = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8);
                if (logger.isDebugEnabled()) {
                    logger.debug("encrypt initKey: {}", Base64.getEncoder().encode(initKey));
                }

                sm2Engine.init(true, new ParametersWithRandom(keyParam, new SecureRandom(initKey)));
                if (logger.isDebugEnabled()) {
                    logger.debug("encrypt init");
                }
            } catch (Exception var14) {
                e = var14;
                e.printStackTrace();
                throw new SecurityException("加载加密密钥异常," + e.getMessage(), e);
            }

            try {
                return sm2Engine.processBlock(msg, 0, msg.length);
            } catch (Exception var13) {
                e = var13;
                e.printStackTrace();
                throw new SecurityException("加密时出现异常," + e.getMessage(), e);
            }
        }
    }

    public byte[] decrypt(byte[] msg) throws SecurityException {
        if (this.decryptKey == null) {
            throw new SecurityException("未初始化解密密钥");
        } else {
            SM2Engine.Mode mode;
            switch (this.decryptMode == null ? "C1C3C2" : this.decryptMode) {
                case "C1C2C3":
                    mode = SM2Engine.Mode.C1C2C3;
                    break;
                case "C1C3C2":
                    mode = SM2Engine.Mode.C1C3C2;
                    break;
                default:
                    throw new SecurityException("暂不支持的加密模式");
            }

            SM2Engine sm2Engine;
            Exception e;
            try {
                sm2Engine = new SM2Engine(mode);
                BigInteger bigInteger = new BigInteger(this.decryptKey);
                ECPrivateKey keyObj = (ECPrivateKey) FACTORY.generatePrivate(new ECPrivateKeySpec(bigInteger, EC_SPEC));
                ECKeyParameters keyParam = new ECPrivateKeyParameters(keyObj.getD(), DOMAIN_PARAMS);
                sm2Engine.init(false, keyParam);
            } catch (Exception var13) {
                e = var13;
                throw new SecurityException("加载加密密钥异常," + e.getMessage(), e);
            }

            try {
                return sm2Engine.processBlock(msg, 0, msg.length);
            } catch (Exception var12) {
                e = var12;
                throw new SecurityException("解密时出现异常," + e.getMessage(), e);
            }
        }
    }

    public byte[] sign(byte[] msg) throws SecurityException {
        if (this.signKey == null) {
            throw new SecurityException("未初始化签名密钥");
        } else {
            SM2Signer signer;
            Throwable e;
            try {
                BigInteger d = new BigInteger(1, this.signKey);
                ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
                ECDomainParameters ecParameters = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
                ECPrivateKeyParameters privkey = new ECPrivateKeyParameters(d, ecParameters);
                signer = new SM2Signer();
                ParametersWithID parameters = new ParametersWithID(privkey, this.signUserID == null ? DEFAULT_SIGN_USERID : this.signUserID);
                signer.init(true, parameters);
            } catch (Throwable var9) {
                e = var9;
                throw new SecurityException("加载签名密钥失败," + e.getMessage());
            }

            try {
                signer.update(msg, 0, msg.length);
                return signer.generateSignature();
            } catch (Throwable var8) {
                e = var8;
                throw new SecurityException("签名时出现异常," + e.getMessage(), e);
            }
        }
    }

    public boolean verify(byte[] msg, byte[] sign) throws SecurityException {
        if (this.verifyKey == null) {
            throw new SecurityException("未初始化验签密钥");
        } else {
            SM2Signer signer;
            try {
                byte[] x = new byte[32];
                byte[] y = new byte[32];
                System.arraycopy(this.verifyKey, 1, x, 0, 32);
                System.arraycopy(this.verifyKey, 33, y, 0, 32);
                BigInteger X = new BigInteger(1, x);
                BigInteger Y = new BigInteger(1, y);
                ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
                ECPoint Q = spec.getCurve().createPoint(X, Y);
                ECDomainParameters ecParameters = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
                ECPublicKeyParameters pubKeyParam = new ECPublicKeyParameters(Q, ecParameters);
                signer = new SM2Signer();
                ParametersWithID parameters = new ParametersWithID(pubKeyParam, this.verifyUserID == null ? DEFAULT_VERIFY_USERID : this.verifyUserID);
                signer.init(false, parameters);
            } catch (Throwable var14) {
                Throwable e = var14;
                throw new SecurityException("加载验签密钥失败," + e.getMessage());
            }

            try {
                signer.update(msg, 0, msg.length);
                return signer.verifySignature(sign);
            } catch (Exception var13) {
                Exception e = var13;
                throw new SecurityException("验签时出现异常," + e.getMessage(), e);
            }
        }
    }

    public byte[] verifyKey() {
        return this.verifyKey;
    }

    public GMCertificate verifyKey(byte[] verifyKey) {
        this.verifyKey = verifyKey;
        return this;
    }

    public byte[] signKey() {
        return this.signKey;
    }

    public GMCertificate signKey(byte[] signKey) {
        this.signKey = signKey;
        return this;
    }

    public byte[] signUserID() {
        return this.signUserID;
    }

    public GMCertificate signUserID(byte[] signUserID) {
        this.signUserID = signUserID;
        return this;
    }

    public byte[] verifyUserID() {
        return this.verifyUserID;
    }

    public GMCertificate verifyUserID(byte[] verifyUserID) {
        this.verifyUserID = verifyUserID;
        return this;
    }

    public byte[] encryptKey() {
        return this.encryptKey;
    }

    public GMCertificate encryptKey(byte[] encryptKey) {
        this.encryptKey = encryptKey;
        return this;
    }

    public String encryptMode() {
        return this.encryptMode;
    }

    public GMCertificate encryptMode(String encryptMode) {
        this.encryptMode = encryptMode;
        return this;
    }

    public byte[] decryptKey() {
        return this.decryptKey;
    }

    public GMCertificate decryptKey(byte[] decryptKey) {
        this.decryptKey = decryptKey;
        return this;
    }

    public String decryptMode() {
        return this.decryptMode;
    }

    public GMCertificate decryptMode(String decryptMode) {
        this.decryptMode = decryptMode;
        return this;
    }
}

